#include "stdafx.h"
#include "tileengine.h"

#include "camera.h"
#include "xml/domnodebuilder.h"
#include "glc/clientstate.h"
#include "glc/shading/directlight.h"
#include <graphics/maprenderer.h>
#include <data/map/globalmap.hpp>
#include "mapgenerator.hpp"

#include <core/coresettings.h>

#include <math.h>
#include <QtXml/QDomDocument>

#include <QtCore/QDebug>

namespace Demo {

struct CameraMovingState
{
    bool shiftingLeft;
    bool shiftingRight;
    bool shiftingForward;
    bool shiftingBack;

    CameraMovingState()
        :shiftingLeft(false)
        ,shiftingRight(false)
        ,shiftingForward(false)
        ,shiftingBack(false)
    {
    }

    /**
     * @brief apply applies moving state to given camera (moves it once per timer tick)
     * @param camera
     */
    void apply(Camera &camera)
    {
        if (shiftingLeft)
            camera.shiftLeft();
        else if (shiftingRight)
            camera.shiftRight();
        if (shiftingForward)
            camera.shiftForward();
        else if (shiftingBack)
            camera.shiftBack();
    }
};

class TileEnginePrivate
{
public:
    TileEnginePrivate()
        :_sunlight(GL_LIGHT0)
        ,_mousePos(400, 300)
    {
        _sunlight.setAmbientIntensity(0.1, 0.1, 0.1);
        _sunlight.setDiffuseIntensity(0.2, 0.2, 0.2);
        _sunlight.setSpecularIntensity(0.2, 0.2, 0.2);
    }

    ~TileEnginePrivate()
    {
    }

    void createMap()
    {
        MapGeneratorOptions options;
        options.landscapeMethod = MapGeneratorOptions::LandscapeAprilRandom;
        MapGenerator gen(options);
        gen.generate(_globalMap);
        qDebug() << "Map created";
        _mapRenderer.invalidate();
    }

    void loadMapXML(const QString &path)
    {
        QDomDocument document;
        DomNodeBuilder::readDocument(path, document);
        DomNodeBuilder::buildMap(document, _globalMap);
        qDebug() << "Map loaded from " << path;
        _mapRenderer.invalidate();
    }

    void saveMapXML(const QString &path)
    {
        QDomDocument document;
        DomNodeBuilder::buildDocument(_globalMap, document);
        DomNodeBuilder::writeDocument(document, path);
        qDebug() << "Map saved to " << path;
    }

    void draw()
    {
        _cameraState.apply(_camera);
        _camera.refresh();
        _sunlight.enable();
        _mapRenderer.render(_globalMap);
    }

    void onKeyDown(QKeyEvent *event)
    {
        switch (event->key())
        {
            case Qt::Key_Right:
            case Qt::Key_D:
                _cameraState.shiftingRight = true;
                break;
            case Qt::Key_Left:
            case Qt::Key_A:
                _cameraState.shiftingLeft = true;
                break;
            case Qt::Key_Up:
            case Qt::Key_W:
                _cameraState.shiftingForward = true;
                break;
            case Qt::Key_Down:
            case Qt::Key_S:
                _cameraState.shiftingBack = true;
                break;
            case Qt::Key_Equal:
                _camera.zoomIn();
                break;
            case Qt::Key_Minus:
                _camera.zoomOut();
                break;
            default:
                break;
        }
    }

    void onKeyUp(QKeyEvent *event)
    {
        switch (event->key())
        {
            case Qt::Key_Right:
            case Qt::Key_D:
                _cameraState.shiftingRight = false;
                break;
            case Qt::Key_Left:
            case Qt::Key_A:
                _cameraState.shiftingLeft = false;
                break;
            case Qt::Key_Up:
            case Qt::Key_W:
                _cameraState.shiftingForward = false;
                break;
            case Qt::Key_Down:
            case Qt::Key_S:
                _cameraState.shiftingBack = false;
                break;
            default:
                break;
        }
    }

    void onMouseDown(QMouseEvent *event)
    {
        switch (event->button())
        {
        case Qt::LeftButton:
            detectMouseClick(event);
            break;

        case Qt::RightButton:
            _mousePos.setX(event->x());
            _mousePos.setY(event->y());
            break;

        default:
            break;
        }
    }

    void onMouseMove(QMouseEvent *event)
    {
        if (event->buttons() & Qt::RightButton)
        {
            _camera.turnAroundZ(event->x() - _mousePos.x());
            _camera.turnAroundX(event->y() - _mousePos.y());
            _mousePos.setX(event->x());
            _mousePos.setY(event->y());
        }
    }

    void onWheelMove(QWheelEvent *event)
    {
        if (event->delta() < 0)
            _camera.zoomOut();
        else
            _camera.zoomIn();
    }

private:
    bool detectMouseClick(QMouseEvent *event)
    {
        vec2d pointXY(event->x(), event->y());
        vec3d pointInGameSpace = _camera.unProject(pointXY);
        uint x = floor(pointInGameSpace.x + _globalMap.width() * 0.5);
        uint y = floor(pointInGameSpace.y + _globalMap.height() * 0.5);
        bool clicked = (x < _globalMap.width()) && (y < _globalMap.height());
        if (clicked)
        {
            myDebug() << "You have clicked tile ("
                      << x
                      << ";"
                      << y
                      << ")\n";
        }
        return clicked;
        return false;
    }

    GLC::DirectLight _sunlight;
    Data::GlobalMap _globalMap;
    Graphics::MapRenderer _mapRenderer;
    Camera _camera;
    QPoint _mousePos;
    CameraMovingState _cameraState;
};

TileEngine::TileEngine()
    :d(new TileEnginePrivate())
{
}

TileEngine::~TileEngine()
{
    delete d;
}

void TileEngine::initialization()
{
    myDebug() << "Camera controls:\n"
              << "  rotating: press right mouse button and move cursor\n"
              << "  scaling: rotate mouse wheel\n"
              << "enjoy!\n";

    glClearColor(0.8, 0.8, 1, 1);
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_CULL_FACE);
    glEnable(GL_LIGHTING);

    Core::CoreSettings *settings = Core::CoreSettings::instance();

    mat4d projection;
    projection.loadPerspective(60.0,
                               double(settings->windowWidth())
                               / double(settings->windowHeight()),
                               1.0, 100.0);
    GLC::ClientState::loadProjection(projection);

    if (glGetError() != GL_NO_ERROR)
    {
        myDebug() << "OpenGL error occured in TileEngine::initialization()\n";
    }

    d->createMap();
}

void TileEngine::logic(const long &elapsedTimeMs)
{
    (void)elapsedTimeMs;
}

void TileEngine::draw()
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    d->draw();
    if (glGetError() != GL_NO_ERROR)
    {
        myDebug() << "OpenGL error occured in TileEngine::draw()\n";
    }
}

void TileEngine::onKeyDown(QKeyEvent *event)
{
    d->onKeyDown(event);
}

void TileEngine::onKeyUp(QKeyEvent *event)
{
    d->onKeyUp(event);
}

void TileEngine::onMouseDown(QMouseEvent *event)
{
    d->onMouseDown(event);
}

void TileEngine::onMouseMove(QMouseEvent *event)
{
    d->onMouseMove(event);
}

void TileEngine::onWheelMove(QWheelEvent *event)
{
    d->onWheelMove(event);
}

void TileEngine::onMouseUp(QMouseEvent *event)
{
    (void)event;
}

void TileEngine::createMap()
{
    d->createMap();
}

void TileEngine::loadMap(const QString &path)
{
    d->loadMapXML(path);
}

void TileEngine::saveMap(const QString &path) const
{
    d->saveMapXML(path);
}

} // namespace Demo
